/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2002-2006 * Sleepycat Software. All rights reserved. * * $Id: INFileReader.java,v 1.1 2006/05/06 08:59:55 ckaestne Exp $ */ package com.sleepycat.je.log; import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.cleaner.TrackedFileSummary; import com.sleepycat.je.cleaner.UtilizationTracker; import com.sleepycat.je.dbi.DatabaseId; import com.sleepycat.je.dbi.DbTree; import com.sleepycat.je.dbi.EnvironmentImpl; import com.sleepycat.je.log.entry.INContainingEntry; import com.sleepycat.je.log.entry.INLogEntry; import com.sleepycat.je.log.entry.LNLogEntry; import com.sleepycat.je.log.entry.LogEntry; import com.sleepycat.je.log.entry.NodeLogEntry; import com.sleepycat.je.tree.FileSummaryLN; import com.sleepycat.je.tree.IN; import com.sleepycat.je.tree.INDeleteInfo; import com.sleepycat.je.tree.INDupDeleteInfo; import com.sleepycat.je.tree.MapLN; import com.sleepycat.je.utilint.DbLsn; /** * INFileReader supports recovery by scanning log files during the IN rebuild * pass. It looks for internal nodes (all types), segregated by whether they * belong to the main tree or the duplicate trees. * * <p>This file reader can also be run in tracking mode to keep track of the * maximum node id, database id and txn id seen so those sequences can be * updated properly at recovery. In this mode it also performs utilization * counting. It is only run once in tracking mode per recovery, in the * first phase of recovery.</p> */ public class INFileReader extends FileReader { /* Information about the last entry seen. */ private boolean lastEntryWasDelete; private boolean lastEntryWasDupDelete; private LogEntryType fromLogType; private boolean isProvisional; /* * targetEntryMap maps DbLogEntryTypes to log entries. We use this * collection to find the right LogEntry instance to read in the * current entry. */ private Map targetEntryMap; private LogEntry targetLogEntry; /* * For tracking non-target log entries. * Note that dbIdTrackingEntry and txnIdTrackingEntry do not overlap with * targetLogEntry, since the former are LNs and the latter are INs. * But nodeTrackingEntry and inTrackingEntry can overlap with the others, * and we only load one of them when they do overlap. */ private Map dbIdTrackingMap; private LNLogEntry dbIdTrackingEntry; private Map txnIdTrackingMap; private LNLogEntry txnIdTrackingEntry; private Map otherNodeTrackingMap; private NodeLogEntry nodeTrackingEntry; private INLogEntry inTrackingEntry; private LNLogEntry fsTrackingEntry; /* * If trackIds is true, peruse all node entries for the maximum * node id, check all MapLNs for the maximum db id, and check all * LNs for the maximum txn id */ private boolean trackIds; private long maxNodeId; private int maxDbId; private long maxTxnId; private boolean mapDbOnly; /* Used for utilization tracking. */ private long partialCkptStart; private UtilizationTracker tracker; private Map fileSummaryLsns; /** * Create this reader to start at a given LSN. */ public INFileReader(EnvironmentImpl env, int readBufferSize, long startLsn, long finishLsn, boolean trackIds, boolean mapDbOnly, long partialCkptStart, Map fileSummaryLsns) throws IOException, DatabaseException { super(env, readBufferSize, true, startLsn, null, DbLsn.NULL_LSN, finishLsn); this.trackIds = trackIds; this.mapDbOnly = mapDbOnly; targetEntryMap = new HashMap(); if (trackIds) { maxNodeId = 0; maxDbId = 0; tracker = env.getUtilizationTracker(); this.partialCkptStart = partialCkptStart; this.fileSummaryLsns = fileSummaryLsns; fsTrackingEntry = (LNLogEntry) LogEntryType.LOG_FILESUMMARYLN.getNewLogEntry(); dbIdTrackingMap = new HashMap(); txnIdTrackingMap = new HashMap(); otherNodeTrackingMap = new HashMap(); dbIdTrackingMap.put(LogEntryType.LOG_MAPLN_TRANSACTIONAL, LogEntryType.LOG_MAPLN_TRANSACTIONAL. getNewLogEntry()); dbIdTrackingMap.put(LogEntryType.LOG_MAPLN, LogEntryType.LOG_MAPLN.getNewLogEntry()); txnIdTrackingMap.put(LogEntryType.LOG_LN_TRANSACTIONAL, LogEntryType.LOG_LN_TRANSACTIONAL. getNewLogEntry()); txnIdTrackingMap.put(LogEntryType.LOG_MAPLN_TRANSACTIONAL, LogEntryType.LOG_MAPLN_TRANSACTIONAL. getNewLogEntry()); txnIdTrackingMap.put(LogEntryType.LOG_NAMELN_TRANSACTIONAL, LogEntryType.LOG_NAMELN_TRANSACTIONAL. getNewLogEntry()); txnIdTrackingMap.put(LogEntryType.LOG_DEL_DUPLN_TRANSACTIONAL, LogEntryType.LOG_DEL_DUPLN_TRANSACTIONAL. getNewLogEntry()); txnIdTrackingMap.put(LogEntryType.LOG_DUPCOUNTLN_TRANSACTIONAL, LogEntryType.LOG_DUPCOUNTLN_TRANSACTIONAL. getNewLogEntry()); } } /** * Configure this reader to target this kind of entry. */ public void addTargetType(LogEntryType entryType) throws DatabaseException { targetEntryMap.put(entryType, entryType.getNewLogEntry()); } /** * If we're tracking node, database and txn ids, we want to see all node * log entries. If not, we only want to see IN entries. * @return true if this is an IN entry. */ protected boolean isTargetEntry(byte entryTypeNum, byte entryTypeVersion) throws DatabaseException { lastEntryWasDelete = false; lastEntryWasDupDelete = false; targetLogEntry = null; dbIdTrackingEntry = null; txnIdTrackingEntry = null; nodeTrackingEntry = null; inTrackingEntry = null; fsTrackingEntry = null; isProvisional = LogEntryType.isProvisional(entryTypeVersion); /* Get the log entry type instance we need to read the entry. */ fromLogType = LogEntryType.findType(entryTypeNum, entryTypeVersion); LogEntry possibleTarget = (LogEntry) targetEntryMap.get(fromLogType); /* * If the entry is provisional, we won't be reading it in its entirety; * otherwise, we try to establish targetLogEntry. */ if (!isProvisional) { targetLogEntry = possibleTarget; } /* Was the log entry an IN deletion? */ if (LogEntryType.LOG_IN_DELETE_INFO.equals(fromLogType)) { lastEntryWasDelete = true; } if (LogEntryType.LOG_IN_DUPDELETE_INFO.equals(fromLogType)) { lastEntryWasDupDelete = true; } if (trackIds) { /* * Check if it's a db or txn id tracking entry. Note that these * entries do not overlap with targetLogEntry. */ if (!isProvisional) { dbIdTrackingEntry = (LNLogEntry) dbIdTrackingMap.get(fromLogType); txnIdTrackingEntry = (LNLogEntry) txnIdTrackingMap.get(fromLogType); } /* * Determine nodeTrackingEntry, inTrackingEntry, fsTrackingEntry. * Note that these entries do overlap with targetLogEntry. */ if (fromLogType.isNodeType()) { if (possibleTarget != null) { nodeTrackingEntry = (NodeLogEntry) possibleTarget; } else if (dbIdTrackingEntry != null) { nodeTrackingEntry = dbIdTrackingEntry; } else if (txnIdTrackingEntry != null) { nodeTrackingEntry = txnIdTrackingEntry; } else { nodeTrackingEntry = (NodeLogEntry) otherNodeTrackingMap.get(fromLogType); if (nodeTrackingEntry == null) { nodeTrackingEntry = (NodeLogEntry) fromLogType.getNewLogEntry(); otherNodeTrackingMap.put(fromLogType, nodeTrackingEntry); } } if (nodeTrackingEntry instanceof INLogEntry) { inTrackingEntry = (INLogEntry) nodeTrackingEntry; } if (LogEntryType.LOG_FILESUMMARYLN.equals(fromLogType)) { fsTrackingEntry = (LNLogEntry) nodeTrackingEntry; } } /* Count all entries as new. */ tracker.countNewLogEntry(getLastLsn(), fromLogType, LogManager.HEADER_BYTES + currentEntrySize); /* * Return true if this entry should be passed on to processEntry. * If we're tracking ids, return if this is a targeted entry * or if it's any kind of tracked entry or node. */ return (targetLogEntry != null) || (dbIdTrackingEntry != null) || (txnIdTrackingEntry != null) || (nodeTrackingEntry != null); } else { /* * Return true if this entry should be passed on to processEntry. * If we're not tracking ids, only return true if it's a targeted * entry. */ return (targetLogEntry != null); } } /** * This reader looks at all nodes for the max node id and database id. It * only returns non-provisional INs and IN delete entries. */ protected boolean processEntry(ByteBuffer entryBuffer) throws DatabaseException { boolean useEntry = false; boolean entryLoaded = false; /* If this is a targetted entry, read the entire log entry. */ if (targetLogEntry != null) { targetLogEntry.readEntry(entryBuffer, currentEntrySize, currentEntryTypeVersion, true); DatabaseId dbId = getDatabaseId(); boolean isMapDb = dbId.equals(DbTree.ID_DB_ID); useEntry = (!mapDbOnly || isMapDb); entryLoaded = true; } /* Do a partial load during tracking if necessary. */ if (trackIds) { /* * Do partial load of db and txn id tracking entries if necessary. * Note that these entries do not overlap with targetLogEntry. * * XXX We're doing a full load for now, since LNLogEntry does not * read the db and txn id in a partial load, only the node id. */ LNLogEntry lnEntry = null; if (dbIdTrackingEntry != null) { /* This entry has a db id */ lnEntry = dbIdTrackingEntry; lnEntry.readEntry(entryBuffer, currentEntrySize, currentEntryTypeVersion, true /* full load */); entryLoaded = true; MapLN mapLN = (MapLN) lnEntry.getMainItem(); int dbId = mapLN.getDatabase().getId().getId(); if (dbId > maxDbId) { maxDbId = dbId; } } if (txnIdTrackingEntry != null) { /* This entry has a txn id */ if (lnEntry == null) { lnEntry = txnIdTrackingEntry; lnEntry.readEntry(entryBuffer, currentEntrySize, currentEntryTypeVersion, true /* full load */); entryLoaded = true; } long txnId = lnEntry.getTxnId().longValue(); if (txnId > maxTxnId) { maxTxnId = txnId; } } /* * Perform utilization counting under trackIds to prevent * double-counting. */ if (fsTrackingEntry != null) { /* Must do full load to get key from file summary LN. */ if (!entryLoaded) { nodeTrackingEntry.readEntry(entryBuffer, currentEntrySize, currentEntryTypeVersion, true /* full load */); entryLoaded = true; } /* * When a FileSummaryLN is encountered, reset the tracked * summary for that file to replay what happens when a * FileSummaryLN log entry is written. */ byte[] keyBytes = fsTrackingEntry.getKey(); FileSummaryLN fsln = (FileSummaryLN) fsTrackingEntry.getMainItem(); long fileNum = fsln.getFileNumber(keyBytes); TrackedFileSummary trackedLN = tracker.getTrackedFile(fileNum); if (trackedLN != null) { trackedLN.reset(); } /* Save the LSN of the FileSummaryLN for use by undo/redo. */ fileSummaryLsns.put(new Long(fileNum), new Long(getLastLsn())); /* * SR 10395: Do not cache the file summary in the * UtilizationProfile here, since it may be for a deleted log * file. */ } /* * Do partial load of nodeTrackingEntry (and inTrackingEntry) if * not already loaded. We only need the node id. */ if (nodeTrackingEntry != null) { if (!entryLoaded) { nodeTrackingEntry.readEntry(entryBuffer, currentEntrySize, currentEntryTypeVersion, false /* partial load */); entryLoaded = true; } /* Keep track of the largest node id seen. */ long nodeId = nodeTrackingEntry.getNodeId(); maxNodeId = (nodeId > maxNodeId) ? nodeId: maxNodeId; } if (inTrackingEntry != null) { assert entryLoaded : "All nodes should have been loaded"; /* * Count the obsolete LSN of the previous version, if available * and if not already counted. Use inexact counting for two * reasons: 1) we don't always have the full LSN because * earlier log versions only had the file number, and 2) we * can't guarantee obsoleteness for provisional INs. */ long oldLsn = inTrackingEntry.getObsoleteLsn(); if (oldLsn != DbLsn.NULL_LSN) { long newLsn = getLastLsn(); if (!isObsoleteLsnAlreadyCounted(oldLsn, newLsn)) { tracker.countObsoleteNodeInexact(oldLsn, fromLogType); } } /* * Count a provisional IN as obsolete if it follows * partialCkptStart. It cannot have been already counted, * because provisional INs are not normally counted as * obsolete; they are only considered obsolete when they are * part of a partial checkpoint. * * Depending on the exact point at which the checkpoint was * aborted, this technique is not always accurate; therefore * inexact counting must be used. */ if (isProvisional && partialCkptStart != DbLsn.NULL_LSN) { oldLsn = getLastLsn(); if (DbLsn.compareTo(partialCkptStart, oldLsn) < 0) { tracker.countObsoleteNodeInexact(oldLsn, fromLogType); } } } } /* Return true if this entry should be processed */ return useEntry; } /** * Returns whether a given obsolete LSN has already been counted in the * utilization profile. If true is returned, it should not be counted * again, to prevent double-counting. */ private boolean isObsoleteLsnAlreadyCounted(long oldLsn, long newLsn) { /* If the file summary follows the new LSN, it was already counted. */ Long fileNum = new Long(DbLsn.getFileNumber(oldLsn)); long fileSummaryLsn = DbLsn.longToLsn((Long) fileSummaryLsns.get(fileNum)); int cmpFsLsnToNewLsn = (fileSummaryLsn != DbLsn.NULL_LSN) ? DbLsn.compareTo(fileSummaryLsn, newLsn) : -1; return (cmpFsLsnToNewLsn >= 0); } /** * Get the last IN seen by the reader. */ public IN getIN() throws DatabaseException { return ((INContainingEntry) targetLogEntry).getIN(env); } /** * Get the last databaseId seen by the reader. */ public DatabaseId getDatabaseId() { if (lastEntryWasDelete) { return ((INDeleteInfo) targetLogEntry.getMainItem()). getDatabaseId(); } else if (lastEntryWasDupDelete) { return ((INDupDeleteInfo) targetLogEntry.getMainItem()). getDatabaseId(); } else { return ((INContainingEntry) targetLogEntry).getDbId(); } } /** * Get the maximum node id seen by the reader. */ public long getMaxNodeId() { return maxNodeId; } /** * Get the maximum db id seen by the reader. */ public int getMaxDbId() { return maxDbId; } /** * Get the maximum txn id seen by the reader. */ public long getMaxTxnId() { return maxTxnId; } /** * @return true if the last entry was a delete info entry. */ public boolean isDeleteInfo() { return lastEntryWasDelete; } /** * @return true if the last entry was a dup delete info entry. */ public boolean isDupDeleteInfo() { return lastEntryWasDupDelete; } /** * Get the deleted node id stored in the last delete info log entry. */ public long getDeletedNodeId() { return ((INDeleteInfo) targetLogEntry.getMainItem()).getDeletedNodeId(); } /** * Get the deleted id key stored in the last delete info log entry. */ public byte[] getDeletedIdKey() { return ((INDeleteInfo) targetLogEntry.getMainItem()).getDeletedIdKey(); } /** * Get the deleted node id stored in the last delete info log entry. */ public long getDupDeletedNodeId() { return ((INDupDeleteInfo) targetLogEntry.getMainItem()).getDeletedNodeId(); } /** * Get the deleted main key stored in the last delete info log entry. */ public byte[] getDupDeletedMainKey() { return ((INDupDeleteInfo) targetLogEntry.getMainItem()).getDeletedMainKey(); } /** * Get the deleted main key stored in the last delete info log entry. */ public byte[] getDupDeletedDupKey() { return ((INDupDeleteInfo) targetLogEntry.getMainItem()).getDeletedDupKey(); } /** * Get the LSN that should represent this IN. For most INs, it's the LSN * that was just read. For BINDelta entries, it's the LSN of the last * full version. */ public long getLsnOfIN() { return ((INContainingEntry) targetLogEntry).getLsnOfIN(getLastLsn()); } /** * Get the current log entry type. */ public LogEntryType getLogEntryType() { return LogEntryType.findType(currentEntryTypeNum, currentEntryTypeVersion); } }